use super::apply_selection;
use crate::{
    error::{Error, Result},
    sync::{
        diff::DiffLinePosition,
        patches::get_file_diff_patch_and_hunklines, utils::repo,
    },
};
use scopetime::scope_time;
use std::path::Path;

///
pub fn stage_lines(
    repo_path: &str,
    file_path: &str,
    is_stage: bool,
    lines: &[DiffLinePosition],
) -> Result<()> {
    scope_time!("stage_lines");

    if lines.is_empty() {
        return Ok(());
    }

    let repo = repo(repo_path)?;
    // log::debug!("stage_lines: {:?}", lines);

    let mut index = repo.index()?;
    index.read(true)?;
    let mut idx =
        index.get_path(Path::new(file_path), 0).ok_or_else(|| {
            Error::Generic(String::from(
                "only non new files supported",
            ))
        })?;
    let blob = repo.find_blob(idx.id)?;
    let indexed_content = String::from_utf8(blob.content().into())?;

    let new_content = {
        let (_patch, hunks) = get_file_diff_patch_and_hunklines(
            &repo, file_path, is_stage, false,
        )?;

        let old_lines = indexed_content.lines().collect::<Vec<_>>();

        apply_selection(lines, &hunks, old_lines, is_stage, false)?
    };

    let blob_id = repo.blob(new_content.as_bytes())?;

    idx.id = blob_id;
    idx.file_size = new_content.as_bytes().len() as u32;
    //TODO: can we simply use add_frombuffer?
    index.add(&idx)?;

    index.write()?;
    index.read(true)?;

    Ok(())
}

#[cfg(test)]
mod test {
    use super::*;
    use crate::sync::{
        diff::get_diff,
        tests::{get_statuses, repo_init, write_commit_file},
        utils::{repo_write_file, stage_add_file},
    };

    #[test]
    fn test_stage() {
        static FILE_1: &str = r"0
";

        static FILE_2: &str = r"0
1
2
3
";

        let (path, repo) = repo_init().unwrap();
        let path = path.path().to_str().unwrap();

        write_commit_file(&repo, "test.txt", FILE_1, "c1");

        repo_write_file(&repo, "test.txt", FILE_2).unwrap();

        stage_lines(
            path,
            "test.txt",
            false,
            &[DiffLinePosition {
                old_lineno: None,
                new_lineno: Some(2),
            }],
        )
        .unwrap();

        let diff =
            get_diff(path, String::from("test.txt"), true).unwrap();

        assert_eq!(diff.lines, 3);
        assert_eq!(
            diff.hunks[0].lines[0].content,
            String::from("@@ -1 +1,2 @@\n")
        );
    }

    #[test]
    fn test_unstage() {
        static FILE_1: &str = r"0
";

        static FILE_2: &str = r"0
1
2
3
";

        let (path, repo) = repo_init().unwrap();
        let path = path.path().to_str().unwrap();

        write_commit_file(&repo, "test.txt", FILE_1, "c1");

        repo_write_file(&repo, "test.txt", FILE_2).unwrap();

        assert_eq!(get_statuses(path), (1, 0));

        stage_add_file(path, &Path::new("test.txt")).unwrap();

        assert_eq!(get_statuses(path), (0, 1));

        let diff_before =
            get_diff(path, String::from("test.txt"), true).unwrap();

        assert_eq!(diff_before.lines, 5);

        stage_lines(
            path,
            "test.txt",
            true,
            &[DiffLinePosition {
                old_lineno: None,
                new_lineno: Some(2),
            }],
        )
        .unwrap();

        assert_eq!(get_statuses(path), (1, 1));

        let diff =
            get_diff(path, String::from("test.txt"), true).unwrap();

        assert_eq!(diff.lines, 4);
    }
}
